#!/usr/bin/perl
use Test::More;

BEGIN {
    $basedir = $0;
    $basedir =~ s|(.*)/[^/]*|$1|;
    $fdr_basedir    = "$basedir/../fdreceive";
    $binder_basedir = "$basedir/../binder";

    $test_bpf_count       = 7;
    $test_fdreceive_count = 4;

    $test_count = $test_bpf_count + $test_fdreceive_count;

    # allow info to be shown during tests
    $v = $ARGV[0];
    if ($v) {
        if ( $v ne "-v" ) {
            plan skip_all => "Invalid option (use -v)";
        }
    }
    else {
        $v = " ";
    }

    # Test if Binder is supported
    $test_binder = 0;

    # check if binder driver available and kernel/userspace versions.
    $result = system("/bin/sh $binder_basedir/init_binder.sh $v 2>/dev/null");

    if ( $result >> 8 eq 0 ) {    # NO_BINDER_SUPPORT
        print "Binder not supported by kernel\n";
    }
    elsif ( $result >> 8 eq 1 ) {    # BASE_BINDER_SUPPORT
        $test_binder = 1;
        $test_count += 4;
        $n = " ";                    # Use /dev/binder
    }
    elsif ( $result >> 8 eq 2 ) {    # BINDERFS_SUPPORT
        $test_binder = 1;
        $test_count += 4;
        $n = "-n";                   # Use /dev/binder-test
    }
    elsif ( $result >> 8 eq 3 ) {    # BINDER_VER_ERROR
        print "Binder kernel/userspace versions differ\n";
    }
    else {                           # BINDER_ERROR
        print "Error checking Binder driver\n";
    }

    plan tests => $test_count;
}

#
# These tests are run with: kernel.unprivileged_bpf_disabled = FALSE
#

#
################ Core BPF Tests #######################
#
# BPF map - BPF_MAP_TYPE_ARRAY
$result = system "runcon -t test_bpf_t $basedir/bpf_test -m $v";
ok( $result eq 0 );

# BPF prog - BPF_PROG_TYPE_SOCKET_FILTER
$result = system "runcon -t test_bpf_t $basedir/bpf_test -p $v";
ok( $result eq 0 );

# Deny map_create permission
$result =
  system "runcon -t test_bpf_deny_map_create_t $basedir/bpf_test -m $v 2>&1";
ok($result);

# Deny map_read permission
$result =
  system "runcon -t test_bpf_deny_map_read_t $basedir/bpf_test -m $v 2>&1";
ok($result);

# Deny map_write permission
$result =
  system "runcon -t test_bpf_deny_map_write_t $basedir/bpf_test -m $v 2>&1";
ok($result);

# Deny prog_load permission
$result =
  system "runcon -t test_bpf_deny_prog_load_t $basedir/bpf_test -p $v 2>&1";
ok($result);

# Deny prog_run permission
$result =
  system "runcon -t test_bpf_deny_prog_run_t $basedir/bpf_test -p $v 2>&1";
ok($result);

#
################ BPF Tests for fdreceive #######################
#
# Remove any leftover test file from prior failed runs.
system("rm -rf $basedir/test_sock");

# Start server process in test_fdreceive_server_t.
system("mkfifo $basedir/flag");
if ( ( $pid = fork() ) == 0 ) {
    exec
"runcon -t test_fdreceive_server_t $fdr_basedir/server $basedir/flag $basedir/test_sock";
}

# Wait for it to initialize.
system("read -t 5 <>$basedir/flag");

# Test BPF map & prog fd on transfer:
$result = system
"runcon -t test_fdreceive_bpf_client_t -- $fdr_basedir/client -m $basedir/test_sock";
ok( $result eq 0 );

$result = system
"runcon -t test_fdreceive_bpf_client_t -- $fdr_basedir/client -p $basedir/test_sock";
ok( $result eq 0 );

# Remove BPF prog_run permission from server:
$result = system
"runcon -t test_fdreceive_bpf_client2_t -- $fdr_basedir/client -p $basedir/test_sock";
ok($result);

# Remove BPF map_read permission from server:
$result = system
"runcon -t test_fdreceive_bpf_client3_t -- $fdr_basedir/client -m $basedir/test_sock";
ok($result);

# Kill the server.
kill KILL, $pid;

# Clean up.
system "rm -rf $basedir/test_sock $basedir/flag";

#
################ BPF Tests for binder #######################
#
sub service_start {
    my ( $service, $runcon_args, $args ) = @_;
    my $pid;
    my $flag = $service . "_flag";

    system("mkfifo $basedir/$flag");

    if ( ( $pid = fork() ) == 0 ) {
        exec
"runcon $runcon_args $binder_basedir/$service -f $basedir/$flag $args";
    }

    # Wait for it to initialize.
    system("read -t 5 <>$basedir/$flag");
    return $pid;
}

sub service_end {
    my ( $service, $pid ) = @_;
    my $flag = $service . "_flag";

    kill KILL, $pid;
    waitpid $pid, 0;
    system("rm -f $basedir/$flag");
}

if ($test_binder) {
    ### Test BPF map fd on transfer ##################
    $sm_pid = service_start( "manager", "-t test_binder_bpf_mgr_t", "$n $v" );
    $sp_pid =
      service_start( "service_provider", "-t test_binder_bpf_provider_t",
        "-m $n $v" );

    # Verify that the BPF map fd can be transferred.
    $result =
      system
      "runcon -t test_binder_bpf_client_t $binder_basedir/client $n $v -m -r 1";
    ok( $result eq 0 );

    # Verify BPF no map perms.
    $result = system
"runcon -t test_binder_client_no_bpf_perm_t $binder_basedir/client $n $v -m -r 2 2>&1";
    ok( $result >> 8 eq 141 );

    ### Test BPF prog fd on transfer ##################
    service_end( "service_provider", $sp_pid );
    $sp_pid =
      service_start( "service_provider", "-t test_binder_bpf_provider_t",
        "-p $n $v" );

    # Verify that the BPF prog fd can be transferred.
    $result =
      system
      "runcon -t test_binder_bpf_client_t $binder_basedir/client $n $v -p -r 1";
    ok( $result eq 0 );

    # Verify BPF no prog perms.
    $result = system
"runcon -t test_binder_client_no_bpf_perm_t $binder_basedir/client $n $v -p -r 2 2>&1";
    ok( $result >> 8 eq 141 );

    # Kill the service provider & manager.
    service_end( "service_provider", $sp_pid );
    service_end( "manager",          $sm_pid );

    # Cleanup binderfs stuff.
    system("/bin/sh $binder_basedir/cleanup_binder.sh $v 2>/dev/null");
}

exit;
